////////////////////////////////////////////////////////////////////
//	
//	File: MinimaxPlayer.chpl 
//	Team: Patrick Copeland, Deanna Fink, Jordan Hildebrandt
//	Date: 15 February 2012
//	Time: 19:47
//	Desc: MinimaxPlayer.chpl defines a an computer player
//		  that utilizes the minimax algorithm.
//
////////////////////////////////////////////////////////////////////

use Player, Connect4Controller;

class MinimaxPlayer: Player {
	// ====================
	// instance variables
	// ====================
	
	/**
	 * Symbol used in text display.
	 */
	var symbol:string;

	var alpha:int;

	var beta:int;
	

	//====================
	// public methods
	// ====================

	// TODO: Constructor?
	/**
	 * Class contructor
	 */
	proc MinimaxPlayer(symbol:string,alpha:int,beta:int){
		this.symbol = symbol;
		
		this.alpha = alpha;
		this.beta = beta;
	}


	proc getSymbol() {
		return this.symbol;
	}
	
	// this is poor design
	proc getOpponentSymbol() {
		if this.getSymbol() == "x" {
			return "o";
		} else {
			return "x";
		}
	}

	/**
	 * Returns the move for a player.
	 */
	proc getMove(game:Connect4Game) {
		var bestMoveColumn, maxValue, possMaxValue:int;
		var vectorOfMoves:Vector(Connect4Game);
		
		bestMoveColumn = 0;
		maxValue = 0;
		vectorOfMoves = game.getChildren(this.getSymbol());
		
		for moveIndex in 0..vectorOfMoves.size-1 {
			possMaxValue = this.maxValue(game, this.alpha, this.beta, 3);
			
			if maxValue < possMaxValue {
				maxValue = possMaxValue;
				bestMoveColumn = vectorOfMoves.elementAt(moveIndex).lastMove;
			}
		}
		
		return bestMoveColumn;
	}
	
	
	proc maxValue(game:Connect4Game, localAlpha:int, localBeta:int, ply:int):int {
		var move, bestMove, value, newValue:int;
		var vectorOfMoves:Vector(Connect4Game);
		
		value = -1000000;
		
		if ply == 0 || game.isOver() {
			return this.evalState(game);
		} else {
			vectorOfMoves = game.getChildren(this.getSymbol());
			
			for moveIndex in 0..vectorOfMoves.size-1 {
				newValue = this.minValue(vectorOfMoves.elementAt(moveIndex), localAlpha, localBeta, ply-1);
				
				value = max(value, newValue);
				
				if value >= localBeta {
					return value;
				}
				
				this.alpha = this.max(localAlpha, value);	
			}	
			
			return value;
		}	
	}
	
	proc minValue(game:Connect4Game, localAlpha:int, localBeta:int, ply:int):int {
		var move, bestMove, value, newValue:int;
		var vectorOfMoves:Vector(Connect4Game);
		
		value = 1000000;
		
		if ply == 0 || game.isOver() {
			return this.evalState(game);
		} else {
			vectorOfMoves = game.getChildren(this.getOpponentSymbol());
			
			for moveIndex in 0..vectorOfMoves.size-1 {
				newValue = this.maxValue(vectorOfMoves.elementAt(moveIndex), localAlpha, localBeta, ply-1);
				
				value = min(value, newValue);
				
				if value <= localAlpha {
					return value;
				}
				
				this.beta = this.min(localBeta, value);
			}
		
			return value;
		}
	}
	
	proc evalState(game:Connect4Game) {
		
		var currentBoard = game.getBoard();
		var value: int = 0;

        //check horizontal
        for row in [0..5] {
			for col in [0..6] {
				if col <= 3 {
					if game.isNSeqHoriz(4, currentBoard, row, col, this.symbol) {
						value = value + 200;
					}
					if ((currentBoard[row][col+3]==this.symbol)&&(currentBoard[row][col+2]=='_')&&game.isNSeqHoriz(2, currentBoard, row, col, this.symbol)) {
						value = value + 75;
					}
					if ((currentBoard[row][col]==this.symbol)&&(currentBoard[row][col+1]=='_')&&game.isNSeqHoriz(2, currentBoard, row, col+2, this.symbol)) {
						value = value + 75;
					}
				}
            
				if col <= 4 {
					if game.isNSeqHoriz(3, currentBoard, row, col, this.symbol) {
						value = value + 100;
					}  
				}
            
				if col <=5 {
					if game.isNSeqHoriz(2, currentBoard, row, col, this.symbol) {
						value = value + 25;
					}
				}
			}
		}

        //check vertical
        for row in [0..5] {
			for col in [0..6] {
				if row <= 2 {
					if game.isNSeqVert(4, currentBoard, row, col, this.symbol) {
						value = value + 200;
					}
				}
         
				if row <= 3 {
					if game.isNSeqVert(3, currentBoard, row, col, this.symbol) {
						value = value + 100;
					}
				}
				  
				  
				if row <= 4 {
					if game.isNSeqVert(2, currentBoard, row, col, this.symbol) {
						value = value + 25;
					}
				}
            }
        }
        
        //check upDiag
        for row in [0..5] {
			for col in [0..6] {
				if row >= 2 && col <= 4 {
					if game.isNSeqUpDiag(3, currentBoard, row, col, this.symbol) {
						value = value + 100;
					}
				}
				
				if ((row >= 1) && (col <= 5)) {
					if game.isNSeqUpDiag(2, currentBoard, row, col, this.symbol) {
						value = value + 25;
					}
				}
			}
        }

        //check downDiag
		for row in [0..5] {
			for col in [0..6] {
				if row <= 3 && col <= 4 {
					if game.isNSeqDownDiag(3, currentBoard, row, col, this.symbol) {
						value = value + 100;
					}
				}
				if row <= 4 && col <= 5 {
					if game.isNSeqDownDiag(2, currentBoard, row, col, this.symbol) {
					value = value + 25;
					}
				}
			
			}
        }

        return value;
	}
		
		
		
		
	proc max(left:int, right:int) {
		if left >= right {
			return left;
		} else {
			return right;
		}
	}
	
	proc min(left:int, right:int) {
		if left <= right {
			return left;
		} else {
			return right;
		}
	}
		


}

// end MinimaxPlayer.chpl